import * as THREE from "../../../../modules/three/three.module.js";
import { FXAAShader } from '../../../../modules/three/jsm/shaders/FXAAShader.js';
import {
  EdgeFragment,
  EdgeVertex,
  MaskFragment,
  MaskVertex,
} from './shader_outline_helper.js';

const oldClearColor = new THREE.Color();

class OutlineHelper {
  /**
   * 被隐藏的对象
   * @type {THREE.Object3D[]}
   */
  hideObjects = [];
  /**
   * 选中对象
   * @type {THREE.Object3D[]}
   */
  selectedObjects = [];
  /**
   * 鼠标hover对象
   * @type {THREE.Object3D[]}
   */
  hoverObjects = [];
  /**
   * 额外的 需要标记的对象
   * @type {THREE.Object3D[]}
   */
  thirdObjects = [];

  /**
   * 用于显示3d模型的轮廓
   * @param {THREE.WebGLRenderer} renderer
   * @param {THREE.Scene} renderScene
   * @param {THREE.Camera} renderCamera
   */
  constructor(renderer, renderScene, renderCamera) {
    this.renderer = renderer;
    this.renderScene = renderScene;
    this.renderCamera = renderCamera;

    const scene = new THREE.Scene();
    scene.autoUpdate = false;
    const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    const _geometry = new THREE.BufferGeometry();
    _geometry.setAttribute(
      'position',
      new THREE.Float32BufferAttribute([-1, 3, 0, -1, -1, 0, 3, -1, 0], 3),
    );
    _geometry.setAttribute(
      'uv',
      new THREE.Float32BufferAttribute([0, 2, 0, 0, 2, 0], 2),
    );
    const quad = new THREE.Mesh(_geometry, null);
    this._quad = quad;
    scene.add(quad);
    this._scene = scene;
    this._camera = camera;

    this._maskMaterial = new THREE.ShaderMaterial({
      vertexShader: MaskVertex,
      fragmentShader: MaskFragment,
      depthTest: false,
      side: THREE.DoubleSide,
    });
    this.onCanvasResize();
  }

  setRenderCamera(camera) {
    this.renderCamera = camera;
  }

  _hideNonSelectedObjects(obj, root, selected) {
    if (selected.includes(obj)) {
      let current = obj.parent;
      while (current && current !== root) {
        const index = this.hideObjects.indexOf(current);
        if (index >= 0) {
          this.hideObjects.splice(index, 1);
          current.visible = current.userData.oldVisible;
          delete current.userData.oldVisible;
        }
        current = current.parent;
      }
      return;
    }

    if (obj !== root && !this.hideObjects.includes(obj)) {
      obj.userData.oldVisible = obj.visible;
      obj.visible = false;
      this.hideObjects.push(obj);
    }

    for (const child of obj.children) {
      if (child instanceof THREE.Light) {
        continue;
      }
      this._hideNonSelectedObjects(child, root, selected);
    }
  }

  _showNonSelectedObjects() {
    this.hideObjects.forEach((n) => {
      n.visible = n.userData.oldVisible;
      delete n.userData.oldVisible;
    });
  }

  _drawMask(buffer, visibleObjects) {
    this.hideObjects.length = 0;
    this._hideNonSelectedObjects(
      this.renderScene,
      this.renderScene,
      visibleObjects,
    );

    this.renderScene.overrideMaterial = this._maskMaterial;
    this.renderScene.background = null;

    this.renderer.autoClear = false;
    this.renderer.setRenderTarget(buffer);
    this.renderer.setClearColor(0xffffff);
    this.renderer.setClearAlpha(1);
    this.renderer.clear();

    this.renderer.render(this.renderScene, this.renderCamera);

    this._showNonSelectedObjects();
  }

  onCanvasResize() {
    const size = new THREE.Vector2();
    this.renderer.getDrawingBufferSize(size);
    const width = size.x * 2;
    const height = size.y * 2;
    const params = {
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter,
      format: THREE.RGBAFormat,
      antialias: true,
    };
    this._maskBuffer = new THREE.WebGLRenderTarget(width, height, params);
    this._maskBuffer.texture.generateMipmaps = false;
    this._maskBufferHover = new THREE.WebGLRenderTarget(width, height, params);
    this._maskBufferHover.texture.generateMipmaps = false;
    this._maskBufferThird = new THREE.WebGLRenderTarget(width, height, params);
    this._maskBufferThird.texture.generateMipmaps = false;
    this._edgeBuffer = new THREE.WebGLRenderTarget(width, height, params);
    this._edgeBuffer.texture.generateMipmaps = false;

    const selectedColor = '#ff0000';
    const hoverColor = '#00ffff';
    const thirdColor = '#4b4bd9';
    const selectedThickness = 3;
    this._edgeMaterial = new THREE.ShaderMaterial({
      vertexShader: EdgeVertex,
      fragmentShader: EdgeFragment,
      uniforms: {
        maskTexture: {
          value: this._maskBuffer.texture,
        },
        maskTextureHover: {
          value: this._maskBufferHover.texture,
        },
        maskTextureThird: {
          value: this._maskBufferThird.texture,
        },
        texSize: {
          value: new THREE.Vector2(width, height),
        },
        visibleEdgeColor: {
          value: new THREE.Color(selectedColor),
        },
        hoverColor: {
          value: new THREE.Color(hoverColor),
        },
        thirdColor: {
          value: new THREE.Color(thirdColor),
        },
        thickness: {
          type: 'f',
          value: selectedThickness,
        },
      },
      depthTest: false,
      toneMapped: false,
      side: THREE.DoubleSide,
    });
    this._copyMaterial = new THREE.ShaderMaterial({
      vertexShader: FXAAShader.vertexShader,
      fragmentShader: FXAAShader.fragmentShader,
      uniforms: {
        tDiffuse: {
          value: this._edgeBuffer.texture,
        },
        resolution: {
          value: new THREE.Vector2(1 / width, 1 / height),
        },
      },
      blending: THREE.NormalBlending,
      transparent: true,
      depthTest: false,
      toneMapped: false,
      side: THREE.DoubleSide,
    });
  }

  setSelectColor(cssColorStr) {
    this._edgeMaterial.uniforms.visibleEdgeColor.value.setStyle(cssColorStr);
  }

  setOutlineThickness(thickness) {
    this._edgeMaterial.uniforms.thickness.value = thickness;
  }

  select(obj) {
    if (!this.selectedObjects.includes(obj)) {
      this.selectedObjects.push(obj);
    }
  }

  selectSingle(obj) {
    this.selectedObjects = [obj];
  }

  unSelect() {
    this.selectedObjects = [];
  }

  setHoverSingle(obj) {
    if (!this.selectedObjects.includes(obj)) {
      this.hoverObjects = [obj];
    }
  }

  hover(obj) {
    if (!this.hoverObjects.includes(obj)) {
      this.hoverObjects.push(obj);
    }
  }

  unHover() {
    this.hoverObjects = [];
  }

  markThird(obj) {
    if (!this.thirdObjects.includes(obj)) {
      this.thirdObjects.push(obj);
    }
  }

  unmarkThird() {
    this.thirdObjects = [];
  }

  clearAllOutlines() {
    this.selectedObjects = [];
    this.hoverObjects = [];
    this.thirdObjects = [];
  }

  onAfterRender() {
    if (
      this.selectedObjects.length === 0 &&
      this.hoverObjects.length === 0 &&
      this.thirdObjects.length === 0
    ) {
      return;
    }
    // 记录原始状态
    const oldOverrideMaterial = this.renderScene.overrideMaterial;
    const oldBackground = this.renderScene.background;

    const oldAutoClear = this.renderer.autoClear;
    this.renderer.getClearColor(oldClearColor);
    const oldClearAlpha = this.renderer.getClearAlpha();
    const oldRenderTarget = this.renderer.getRenderTarget();

    // 绘制蒙版
    this._drawMask(this._maskBuffer, this.selectedObjects);

    // 绘制蒙版
    this._drawMask(this._maskBufferHover, this.hoverObjects);
    this._drawMask(this._maskBufferThird, this.thirdObjects);

    // this._edgeMaterial.uniforms.thickness.value = 6 * Math.cos(performance.now() * 0.01 / 5) + 0.5
    // 绘制边框
    this._quad.material = this._edgeMaterial;

    this.renderScene.overrideMaterial = null;

    this.renderer.setRenderTarget(this._edgeBuffer);
    this.renderer.clear();
    this.renderer.render(this._scene, this._camera);

    // 与原场景叠加
    this._quad.material = this._copyMaterial;

    this.renderer.setRenderTarget(null);
    this.renderer.render(this._scene, this._camera);

    // 还原原始状态
    this.renderScene.overrideMaterial = oldOverrideMaterial;
    this.renderScene.background = oldBackground;

    this.renderer.autoClear = oldAutoClear;
    this.renderer.setClearColor(oldClearColor);
    this.renderer.setClearAlpha(oldClearAlpha);
    this.renderer.setRenderTarget(oldRenderTarget);
  }
}

export default OutlineHelper;
